move keyword
https://doc.rust-lang.org/std/keyword.move.html
GPT-4.icon
move キーワードは、クロージャのキャプチャ方法を変更し、参照(& や &mut)ではなく 所有権ごと値をクロージャ内に移動 することを保証します。
1. move の基本的な動作
通常、クロージャは外部の変数を 参照 することでキャプチャします。
しかし、move をつけると 変数の所有権がクロージャに移動 し、元のスコープではその変数を使えなくなります。
例: move を使ったクロージャ
code:rust
let data = vec!1, 2, 3;
// move を使うと、data の所有権が closure に移動する
let closure = move || println!("captured {data:?} by value");
closure();
// data は closure 内に所有権が移っているため、元のスコープでは使えない
// println!("{:?}", data); // コンパイルエラー
このコードでは、move をつけたため data はクロージャに完全に移動し、クロージャの外では data を使うことができなくなります。
2. move クロージャでも Fn や FnMut を実装する場合がある
一見すると、「値の所有権がクロージャに移動したら FnOnce しか実装できないのでは?」と思うかもしれません。しかし、クロージャの 実際の処理内容 によっては Fn や FnMut も実装できます。
例: Fn を実装する move クロージャ
code:rust
fn create_fn() -> impl Fn() {
let text = "Fn".to_owned();
move || println!("This is a: {text}")
}
let fn_plain = create_fn();
fn_plain(); // 何度でも実行可能
fn_plain();
text は move によってクロージャに所有されるが、変更しないため Fn を実装できる。
もし text を 変更 するようなコードが含まれている場合は FnMut になる。
3. move はスレッド (std::thread::spawn) で頻繁に使われる
スレッドを作成するとき、クロージャに外部の変数をキャプチャさせることがあります。
しかし、スレッドは 親スコープとは異なるライフタイム を持つため、通常の借用 (&data) ではスコープ外で変数が消えてしまう可能性があります。
そのため、move を使って変数の所有権をスレッドに渡すのが一般的です。
例: move を使ってスレッドに所有権を渡す
code:rust
use std::thread;
let data = vec!1, 2, 3;
// move をつけることで data の所有権がスレッドに移る
let handle = thread::spawn(move || {
println!("captured {data:?} by value");
});
// スレッドが実行し終わるまで待機
handle.join().unwrap();
// println!("{:?}", data); // コンパイルエラー(data は move されているため)
move をつけないと、data がスレッドの実行中にスコープを抜けて解放される可能性があり、コンパイルエラーになる。
4. move は async と一緒に使える
async ブロックもスレッドと似た動作をするため、クロージャ内の変数を move でキャプチャすることが推奨されることがあります。
例: move を使った async ブロック
code:rust
let capture = "hello".to_owned();
let block = async move {
println!("rust says {capture} from async block");
};
capture の所有権は async ブロック内に移動するため、非同期処理が開始された後でも安全に値を保持できる。